package inabit.data.expression;

import inabit.base.WorkingMemory;
import inabit.data.Variable;
import inabit.data.expression.arithmetic.ArithmeticExpression;

import java.util.ArrayList;
import java.util.List;
/**Represents a relation between a variable and an arithmetic expression
 * @author Rafael Rocha*/
public class RelationalExpression extends ConsequentExpression {
	
	public static final int EQUAL = 0;
	public static final int DIFFERENT = 1;
	public static final int LESS_THAN = 2;
	public static final int GREATER_THAN = 3;
	public static final int LESS_OR_EQUAL = 4;
	public static final int GREATER_OR_EQUAL = 5;

	private int operator;
	private String varId;
	private ArithmeticExpression right;

	public RelationalExpression(String left, int operator,
			ArithmeticExpression right) {
		this.varId = left;
		this.operator = operator;
		this.right = right;
	}

	@Override
	public Expression copy() {
		return new RelationalExpression(varId, operator, right);
	}

	private int notOperator(int operator) {
		switch (operator) {
		case EQUAL:
			return DIFFERENT;
		case DIFFERENT:
			return EQUAL;
		case LESS_THAN:
			return GREATER_OR_EQUAL;
		case LESS_OR_EQUAL:
			return GREATER_THAN;
		case GREATER_THAN:
			return LESS_OR_EQUAL;
		case GREATER_OR_EQUAL:
			return LESS_THAN;
		}
		return -1;

	}

	@Override
	public Double evaluate(WorkingMemory wm) {
		Variable var = wm.getVariable(varId);
		
		String varValue = var.getValue();
		String expValue = right.getResult(wm);
		if (expValue == null) {
			return null;
		}
		int operator = this.operator;
		if (hasNot()) {
			operator = notOperator(this.operator);
		}
		switch (operator) {
		case EQUAL:
			if (varValue == null) {
				if(var.isValidValue(expValue)) {
					return null;
				}else {
					return 0.0;
				}
			}else {
				return varValue.equals(expValue) ? 1.0 : 0.0;
			}
		case DIFFERENT:
			if (varValue == null) {
				if(!var.isValidValue(expValue)) {
					return 1.0;
				}else {
					return null;
				}
			} else {
				return varValue.equals(expValue) ? 0.0 : 1.0;
			}
		case LESS_THAN:
			try {
				Double r = Double.parseDouble(expValue);
				if (varValue == null) {
					
					Boolean b = var.getInfo().isLessThan(r);
					if (b == null)
						return null;
					return b ? 1.0 : 0.0;
				} else {
					return Double.parseDouble(varValue) < r ? 1.0 : 0.0;
				}
			} catch (NumberFormatException e) {
				e.printStackTrace();
				return null;
			}
		case GREATER_THAN:
			try {
				Double r = Double.parseDouble(expValue);
				if (varValue == null) {
					Boolean b = var.getInfo().isGreaterThan(r);
					if (b == null)
						return null;
					return b ? 1.0 : 0.0;
				} else {
					return Double.parseDouble(varValue) > r ? 1.0 : 0.0;
				}
			} catch (NumberFormatException e) {
				e.printStackTrace();
				return null;
			}
		case LESS_OR_EQUAL:
			try {
				Double r = Double.parseDouble(expValue);
				if (varValue == null) {
					Boolean b = var.getInfo().isLessOrEqual(r);
					if (b == null)
						return null;
					return b ? 1.0 : 0.0;
				} else {
					return Double.parseDouble(varValue) <= r ? 1.0 : 0.0;
				}
			} catch (NumberFormatException e) {
				e.printStackTrace();
				return null;
			}
		case GREATER_OR_EQUAL:
			try {
				Double r = Double.parseDouble(expValue);
				if (varValue == null) {

					Boolean b = var.getInfo().isGreaterOrEqual(r);
					if (b == null)
						return null;
					return b ? 1.0 : 0.0;
				} else {
					return Double.parseDouble(varValue) >= r ? 1.0 : 0.0;
				}
			} catch (NumberFormatException e) {
				e.printStackTrace();
				return null;
			}
		}

		return null;
	}

	@Override
	public String stringForm() {
		String op = " <null> ";
		switch (operator) {
		case EQUAL:
			op = " = ";
			break;
		case DIFFERENT:
			op = " != ";
			break;
		case LESS_THAN:
			op = " < ";
			break;
		case LESS_OR_EQUAL:
			op = " <= ";
			break;
		case GREATER_THAN:
			op = " > ";
			break;
		case GREATER_OR_EQUAL:
			op = " >= ";
			break;
		}
		return varId + op + right.stringForm();
	}

	@Override
	public void fire(WorkingMemory wm, double truth) throws Exception {
		String value = right.getResult(wm);
		Variable var = wm.getVariable(varId);
		if (truth > Double.MIN_VALUE) {
			int operator = this.operator;
			if (hasNot()) {
				operator = notOperator(operator);
			}
			if(value == null) {
				throw new Exception("coudn't resolve value for " + var.getID() + " at expression " + this.toString());
			}
			switch (operator) {
			case EQUAL:
				
				if(var.isValidValue(value)) {
					if(var.getValue() == null) {
						var.setValue(value);
					}else {
						try {
							double a = Double.parseDouble(value);
							double b = Double.parseDouble(var.getValue());
							if(a == b) {
								return;
							}
						}catch(NumberFormatException e) {
							if(var.getValue().equals(value)) {
								return;
							}
							throw new Exception("cannot attribute value " + value + " to variable " + var.getID() + ", it already has value " + var.getValue());
						}
						throw new Exception("cannot attribute value " + value + " to variable " + var.getID() + ", it already has value " + var.getValue());
					}
				}else {
					throw new Exception("value " + value + " isn't acceptable for variable " + var.getID());
				}
				break;
			case DIFFERENT:
				if(var.getValue() != null && value.equals(var.getValue())) {
					throw new Exception("variable " + var.getID() + " cannot be different of " + value + " since it's value is " + value);
				}
				var.getInfo().addDifferent(value);
				break;
			case LESS_THAN:
				if(var.isNumeric() && var.getValue() != null && Double.parseDouble(var.getValue()) >= Double.parseDouble(value)) {
					throw new Exception("variable " + var.getID() + " cannot be < " + value + " since it's value is " + value);
				}
				var.getInfo().setLessThan(value);
				break;
			case LESS_OR_EQUAL:
				if(var.isNumeric() && var.getValue() != null && Double.parseDouble(var.getValue()) > Double.parseDouble(value)) {
					throw new Exception("variable " + var.getID() + " cannot be <= " + value + " since it's value is " + value);
				}
				var.getInfo().setLessOrEqual(value);
				break;
			case GREATER_THAN:
				if(var.isNumeric() && var.getValue() != null && Double.parseDouble(var.getValue()) <= Double.parseDouble(value)) {
					throw new Exception("variable " + var.getID() + " cannot be > " + value + " since it's value is " + value);
				}
				var.getInfo().setGreaterThan(value);
				break;
			case GREATER_OR_EQUAL:
				if(var.isNumeric() && var.getValue() != null && Double.parseDouble(var.getValue()) < Double.parseDouble(value)) {
					throw new Exception("variable " + var.getID() + " cannot be >= " + value + " since it's value is " + value);
				}
				var.getInfo().setGreaterOrEqual(value);
				break;
			}
		}
		var.tryAssignValue();
	}

	@Override
	public List<Variable> getTargetVariables(WorkingMemory wm) {
		List<Variable> targets = new ArrayList<Variable>();
		targets.add(wm.getVariable(varId));
		return targets;
	}

	@Override
	public List<String> getReferencedVariablesIds() {
		List<String> referenced = new ArrayList<String>();
		referenced.add(varId);
		referenced.addAll(right.getReferenced());
		return referenced;
	}

}
